// Package oauth2
/**
* @Project : geektime-basic-go-study
* @File    : wechat.go
* @IDE     : GoLand
* @Author  : Tvux
* @Date    : 2024/10/25 09:16
**/

package oauth2

import (
	"errors"
	"fmt"
	"geektime-basic-go-study/webook/backend/internal/service"
	"geektime-basic-go-study/webook/backend/internal/service/oauth2/wechat"
	"geektime-basic-go-study/webook/backend/internal/web"
	ijwt "geektime-basic-go-study/webook/backend/internal/web/jwt"
	"github.com/HJH0924/GenericGo/logger"
	"github.com/gin-gonic/gin"
	"github.com/golang-jwt/jwt/v5"
	"github.com/lithammer/shortuuid/v4"
	"github.com/spf13/viper"
	"net/http"
	"time"
)

var (
	stateKey256 = []byte("9cwTCXNCFKwpnE8VlnWDHZt0GzGXKwJ74NhYLXrkNKKtVPTXFTr57LP0nWOOeYvKh2WzcQqe7KCkMsrelexeDhzTBB9FJQ6SB1XTEi7o4iTzcInkzv8oVg5ld4G32nyVLamN9DQ8ay1AfFich4YlC61fiEnVXTFttuup9IRvAZ3wHr3q7bBKpbZbwv07lGI2ZaTL2Mi7nEnEM79dH4I7MWtaFOHteo6wuZ41VNTeLRNKxBGJjGI6boJJDd8DoSVK")
)

var _ web.Handler = (*WechatHandler)(nil)

type WechatHandler struct {
	wechatSvc wechat.Service
	userSvc   service.UserService
	ijwt.UserJWTHandler
	logger.Logger
}

func NewWechatHandler(wechatSvc wechat.Service, userSvc service.UserService, ujwtHdl ijwt.UserJWTHandler, zl logger.Logger) *WechatHandler {
	return &WechatHandler{
		wechatSvc:      wechatSvc,
		userSvc:        userSvc,
		UserJWTHandler: ujwtHdl,
		Logger:         zl,
	}
}

func (Self *WechatHandler) RegisterRoutes(server *gin.Engine) {
	wg := server.Group("/oauth2/wechat")
	wg.GET("/authurl", Self.AuthURL)
	wg.Any("/callback", Self.CallBack)
}

func (Self *WechatHandler) AuthURL(ctx *gin.Context) {
	state := shortuuid.New()
	authUrl, err := Self.wechatSvc.GetAuthURL(ctx, state)
	if err != nil {
		Self.Debug("WechatHandler.AuthURL: GetAuthURL Failed",
			logger.Field{Key: "error", Val: err})
		ctx.JSON(http.StatusOK, web.Result{
			Code: web.ServerErrNum,
			Msg:  "构造扫码登录URL失败",
		})
		return
	}
	err = Self.setStateCookie(ctx, state)
	if err != nil {
		Self.Debug("WechatHandler.AuthURL: setStateCookie Failed",
			logger.Field{Key: "error", Val: err})
		ctx.JSON(http.StatusOK, web.Result{
			Code: web.ServerErrNum,
			Msg:  "系统错误",
		})
		return
	}
	ctx.JSON(http.StatusOK, web.Result{
		Data: authUrl,
	})
}

func (Self *WechatHandler) setStateCookie(ctx *gin.Context, state string) error {
	stateJWT, err := Self.createStateJWT(state)
	if err != nil {
		return err
	}
	type StateCookie struct {
		name     string
		value    string
		maxAge   int    // Cookie 的最大年龄（以秒为单位），如果是正数，它将覆盖 Expires 字段。
		path     string // Cookie 的路径，指定了 Cookie 适用的路径。如果设置为 /，则适用于整个域名。
		domain   string // Cookie 的域名，指定了 Cookie 适用的域名。
		secure   bool   // 指示 Cookie 仅通过 HTTPS 发送。
		httpOnly bool   // 防止 JavaScript 访问 Cookie。
	}
	stateCookie := StateCookie{
		name:     "jwt-state",
		value:    stateJWT,
		maxAge:   60 * 10,
		path:     "/oauth2/wechar/callback",
		domain:   "",
		secure:   viper.GetBool("config.secure"),
		httpOnly: true,
	}
	ctx.SetCookie(stateCookie.name, stateCookie.value, stateCookie.maxAge, stateCookie.path, stateCookie.domain, stateCookie.secure, stateCookie.httpOnly)
	return nil
}

func (Self *WechatHandler) CallBack(ctx *gin.Context) {
	// 先检查state是不是我原来发的
	err := Self.verifyState(ctx)
	if err != nil {
		Self.Debug("WechatHandler.CallBack: verifyState Failed",
			logger.Field{Key: "error", Val: err})
		ctx.JSON(http.StatusOK, web.Result{
			Code: web.ServerErrNum,
			Msg:  "登录失败",
		})
		return
	}

	code := ctx.Query("code")
	wechatUserInfo, err := Self.wechatSvc.GetWechatUserInfoByCode(ctx, code)
	if err != nil {
		Self.Debug("WechatHandler.CallBack: GetWechatUserInfoByCode Failed",
			logger.Field{Key: "error", Val: err})
		ctx.JSON(http.StatusOK, web.Result{
			Code: web.ServerErrNum,
			Msg:  "系统错误",
		})
		return
	}

	u, err := Self.userSvc.FindOrCreateByWechat(ctx, wechatUserInfo)
	if err != nil {
		Self.Debug("WechatHandler.CallBack: FindOrCreateByWechat Failed",
			logger.Field{Key: "error", Val: err})
		ctx.JSON(http.StatusOK, web.Result{
			Code: web.ServerErrNum,
			Msg:  "系统错误",
		})
		return
	}

	err = Self.SetAccessAndRefreshJWT(ctx, u.Id)
	if err != nil {
		Self.Debug("WechatHandler.CallBack: SetAccessAndRefreshJWT Failed",
			logger.Field{Key: "error", Val: err})
		ctx.JSON(http.StatusOK, web.Result{
			Code: web.ServerErrNum,
			Msg:  "系统错误",
		})
		return
	}

	ctx.JSON(http.StatusOK, web.Result{
		Msg: "登录成功",
	})
}

func (Self *WechatHandler) verifyState(ctx *gin.Context) error {
	stateJWT, err := ctx.Cookie("jwt-state")
	if err != nil {
		return fmt.Errorf("获取'jwt-state' cookie失败: %w", err)
	}

	stateClaims, err := Self.parseStateJWT(stateJWT)
	if err != nil {
		return fmt.Errorf("解析 stateJWT 失败：%w", err)
	}

	state := ctx.Query("state")
	if stateClaims.state != state {
		return errors.New("state 不匹配")
	}
	return nil
}

type StateClaims struct {
	jwt.RegisteredClaims
	state string
}

func (Self *WechatHandler) createStateJWT(state string) (string, error) {
	claims := &StateClaims{
		RegisteredClaims: jwt.RegisteredClaims{
			// 预期中一个用户完成登录的时间
			ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Minute * 10)),
		},
		state: state,
	}
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	stateJWT, err := token.SignedString(stateKey256)
	if err != nil {
		return "", err
	}
	return stateJWT, nil
}

func (Self *WechatHandler) parseStateJWT(stateJWT string) (*StateClaims, error) {
	token, err := jwt.ParseWithClaims(stateJWT, &StateClaims{}, func(token *jwt.Token) (interface{}, error) {
		if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
			return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
		}
		return stateKey256, nil
	})
	if err != nil {
		return nil, err
	}
	if !token.Valid {
		return nil, errors.New("invalid token")
	}
	if claims, ok := token.Claims.(*StateClaims); ok {
		return claims, nil
	} else {
		return nil, errors.New("failed to parse StateClaims")
	}
}
